import Logger from "./utils/Logger"
import TypeLookup from "./utils/TypeLookup"
import OpenDeferred from "./utils/OpenDeferred"
import Data from "./object/Data"
import Module from "./module/Module"
const r = "Data not present.",
  a = new Logger("market")
export default class Market {
  typeLookup: TypeLookup
  store: Record<string, Data>
  moduleRegistry: Record<string, Module>
  firstUpdatePromises: Record<string, OpenDeferred>
  constructor() {
    this.typeLookup = new TypeLookup()
    this.store = {}
    this.moduleRegistry = {}
    this.firstUpdatePromises = {}
  }
  register<A extends typeof Data, B extends Data, C extends Module>(e: C, t: A, i: B) {
    const n = this.typeLookup.getKeyByType(t, !0)
    if (this.moduleRegistry[n]) throw Error("Data can only be registered to one Module")
    this.moduleRegistry[n] = e
    this.store[n] = i
    i.commit()
    this.firstUpdatePromises[n] && (this.firstUpdatePromises[n].resolve(this.store[n]), delete this.firstUpdatePromises[n])
  }
  unregister(t) {
    const i = this.typeLookup.getKeyByType(t, !0)
    if (!this.moduleRegistry[i]) throw Error("No Data was registered to the Module")
    delete this.moduleRegistry[i], delete this.store[i]
  }
  getData<A extends typeof Data = typeof Data>(e: A): A["prototype"] {
    const t = this.typeLookup.getKeyByType(e)
    if (t in this.store) return this.store[t]
    throw Error(r)
  }
  waitForData<A extends typeof Data = typeof Data>(e: A): Promise<A["prototype"]> {
    const n = this.typeLookup.getKeyByType(e, !0)
    return n in this.store
      ? Promise.resolve(this.store[n])
      : (this.firstUpdatePromises[n] ||
          ((this.firstUpdatePromises[n] = new OpenDeferred()),
          setTimeout(() => {
            Logger.level >= 3 && !(n in this.store) && a.debug(`still waiting for ${e.name}-data after 5 seconds`)
          }, 5000)),
        this.firstUpdatePromises[n].nativePromise())
  }
  tryGetData<A extends typeof Data = typeof Data>(e: A): A["prototype"] {
    try {
      return this.getData<A>(e)
    } catch (e) {
      if (e["message"] !== r) throw (a.error("tryGetData", e["message"]), e)
    }
    return null
  }
  debugData() {
    const e = {}
    for (const t in this.moduleRegistry) {
      const i = this.moduleRegistry[t]
      i && (e[i.name] || (e[i.name] = []), e[i.name].push(this.store[t]))
    }
    return Object.freeze(e)
  }
  getModuleNameFromData(e) {
    const t = this.typeLookup.getKeyByType(e)
    if (t) {
      const e = this.moduleRegistry[t]
      if (e) return e.name
    }
    throw Error(r)
  }
}
